// Copyright 2023 Leo <lipf160627@163.com>. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.

package gsm

import (
	"errors"
	"fmt"
	"log"
	"sync"
)

type StateT string
type EventT string
type TransData struct {
	Event EventT
	Data  interface{}
}

type GSM struct {
	Logger *log.Logger

	entries      map[StateT]StateNode
	currentState StateT
	ch           chan TransData
}

type EventNode struct {
	NextState StateT
	Callback  func(*GSM, interface{})
}

type StateNode map[EventT]EventNode

type StateEntry struct {
	State     StateT
	Event     EventT
	NextState StateT
	Callback  func(*GSM, interface{})
}

func (g *GSM) getStateNode(state StateT) (StateNode, error) {
	if v, ok := g.entries[state]; ok {
		return v, nil
	}
	return nil, errors.New("state node not exist")
}

func (g *GSM) getEventNode(state StateT, event EventT) (EventNode, error) {
	if snode, err := g.getStateNode(state); err != nil {
		return EventNode{}, err
	} else {
		if v, ok := snode[event]; ok {
			return v, nil
		} else {
			return EventNode{}, errors.New("event node not exist")
		}
	}
}

// 根据event触发下一个state
func (g *GSM) TriggerToNextState(event EventT, data interface{}) {
	if event != "" {
		g.ch <- TransData{Event: event, Data: data}
	} else {
		close(g.ch)
	}
}

// 创建并运行状态机
// logger: 设置logger
// stateEntries: 状态机表
// firstState: 初始state
// firstEvent: 初始event
// data: 用户数据
// response: 应答回调
func NewAndRun(logger *log.Logger, stateEntries []StateEntry, firstState StateT, firstEvent EventT, userData interface{}) (err error) {
	if len(stateEntries) == 0 {
		return errors.New("state entries must not be nil")
	}

	ch := make(chan TransData)

	g := &GSM{
		Logger:       logger,
		entries:      make(map[StateT]StateNode),
		currentState: firstState,
		ch:           ch,
	}

	for _, stateEntry := range stateEntries {
		if _, ok := g.entries[stateEntry.State]; !ok {
			g.entries[stateEntry.State] = make(StateNode)
		}
		g.entries[stateEntry.State][stateEntry.Event] = EventNode{
			NextState: stateEntry.NextState,
			Callback:  stateEntry.Callback,
		}
	}

	var wg sync.WaitGroup

	// trigger start state
	if eNode, err := g.getEventNode(firstState, firstEvent); err != nil {
		// 当前state通过event没有下一个state，退出
		return err
	} else {
		g.currentState = eNode.NextState
		g.Logger.Printf("State[%s] by event[%s] switch to state[%s]\n",
			firstState, firstEvent, g.currentState)

		go eNode.Callback(g, userData)
	}

	wg.Add(1)
	go func() {
		for {
			data, ok := <-ch
			if !ok {
				g.Logger.Printf("channel closed, done\n")
				break
			}

			currentStateTemp := g.currentState

			if _, err := g.getStateNode(g.currentState); err != nil {
				// 最后一个裸state，结束
				break
			} else {
				if eNode, err := g.getEventNode(g.currentState, data.Event); err != nil {
					// 当前state通过event没有下一个state，错误
					err = fmt.Errorf("state[%s] has not event[%s]", currentStateTemp, data.Event)
					g.Logger.Panicf(err.Error())
				} else {
					g.currentState = eNode.NextState
					g.Logger.Printf("State[%s] by event[%s] switch to state[%s]\n",
						currentStateTemp, data.Event, g.currentState)

					go eNode.Callback(g, data.Data)
				}
			}
		}

		wg.Done()
	}()

	wg.Wait()

	return
}
